﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

using GameLibrary;

namespace UserInterface.Presenters
{
    using Model;
    using Views;
    using Helpers;
    using Events;
    
    [Serializable]
    public class GameInterfaceCoordinator
    {
        #region variables

        private GameManager GameManager = new GameManager();

        [NonSerialized]
        private PlayerMap PlayerMap;

        private Position CurrentUpperLeftCornerPosition;

        private HumanPlayerCoordinator HumanPlayer;

        private FinitStateMachine FinitStateMachine;

        private GameMapElement[,] GameMapElements;

        private GameMapRangeFinder RangeFinder;

        [OnDeserialized]
        private void OnDeSerialized(StreamingContext context)
        {
            this.HumanPlayer.SetHumanPlayer(this.GameManager.HumPlayer);

            this.PlayerMap = MainWindow.CurrentPlayerMap;

            this.ConnectWithPlayerMap();

            this.InitPlayerMap();

            this.Reset();
        }

        #endregion

        #region constructor

        public GameInterfaceCoordinator(PlayerMap map)
        {
            this.PlayerMap = map;
            this.ConnectWithPlayerMap();
            this.CurrentUpperLeftCornerPosition = Position.Zero;
            this.HumanPlayer = new HumanPlayerCoordinator();           
            this.FinitStateMachine = new FinitStateMachine(this);
        }

        #endregion

        #region connect events

        private void ConnectWithPlayerMap()
        {
            this.PlayerMap.LeftButtonClickButton += this.MouseClickButton;
            this.PlayerMap.LeftButtonClickGrid += this.MouseClickGrid;
            this.PlayerMap.KeyPressed += this.KeyPressed;
        }

        #endregion

        #region init game and next turn

        internal void InitGame()
        {
            GameManager.InitGame();

            this.InitPlayerMap();
            this.SetHumanPlayer();
            
            this.Reset();

            ContextMenuCreator.PlayerID = this.HumanPlayer.GetPlayerID();
        }

        private void InitPlayerMap()
        {
            this.PlayerMap.MaxGridColumns = GMHelper.Width;
            this.PlayerMap.MaxGridRows = GMHelper.Height;
            this.PlayerMap.ResetGrid();
            this.PlayerMap.SetMaxSize();
            this.PlayerMap.SetNextTurnVisible();
        }

        private void SetHumanPlayer()
        {
            this.HumanPlayer.SetHumanPlayer(this.GameManager.HumPlayer);
        }

        private void LoadGameMap()
        {
            LoadImageBrush();

            this.GameMapElements = this.HumanPlayer.GetMapElementOnCurrentScreen(
                                        this.CurrentUpperLeftCornerPosition,
                                        this.PlayerMap.GridRowCount,
                                        this.PlayerMap.GridColumnCount);


            this.RangeFinder = new GameMapRangeFinder(this.GameMapElements);

            this.PlayerMap.LoadMapElements(this.GameMapElements);
        }

        private void LoadImageBrush()
        {
            string address = @"../../Resources/Icons/GameObjects/";
            string city = @"City.jpg";
            string village = @"Village.jpg";
            string unit = @"Unit.jpg";
            string mountain = @"Mountain.jpg";
            string plain = @"Plain.jpg";
            string hill = @"Hill.jpg";

            Uri cityImage = new Uri(address + city, UriKind.Relative);
            Uri villageImage = new Uri(address + village, UriKind.Relative);
            Uri unitImage = new Uri(address + unit, UriKind.Relative);
            Uri mountainImage = new Uri(address + mountain, UriKind.Relative);
            Uri plainImage = new Uri(address + plain, UriKind.Relative);
            Uri hillImage = new Uri(address + hill, UriKind.Relative);

            this.PlayerMap.LoadImageBrushes(
                cityImage,
                villageImage,
                unitImage,
                plainImage,
                hillImage,
                mountainImage);
        }

        internal void Reset()
        {   
            GC.Collect();
            GC.WaitForPendingFinalizers();

            this.LoadGameMap();
        }

        internal void NextTurn()
        {
            this.GameManager.NextTurn();

            this.Reset();
        }

        #endregion

        #region unit 

        private void ShowRange(IEnumerable<Position> positions,
                               CommandType command = CommandType.None)
        {
            PlayerMap.ChangeGridMapElementColor(positions, command);
        }

        internal void UnitMoveMode(object unit)
        {
            GameLibrary.Unit.UnitInfo u = unit as GameLibrary.Unit.UnitInfo;
            this.FinitStateMachine.Input(u,
                                        CommandType.UnitMove);
            ShowRange(this.GetMoveRange(u), CommandType.UnitMove);
        }

        internal void UnitMove(GameLibrary.Unit.UnitInfo unit, Position pos)
        {
            this.HumanPlayer.MoveUnitToPosition(unit, pos);
            ShowRange(this.GetMoveRange(unit));
            LoadGameMap();
        }

        internal void UnitAttackMode(object unit)
        {
            this.FinitStateMachine.Input(unit as GameLibrary.Unit.UnitInfo,
                                        CommandType.UnitAttack);    
            this.ShowRange(
                this.GetAttackRange(unit as GameLibrary.Unit.UnitInfo),
                CommandType.UnitAttack);
        }

        internal void UnitAttack(GameLibrary.Unit.UnitInfo unit, Position pos)
        {
           var winLose = this.HumanPlayer.Attack(unit, pos);
           if (winLose == WinLose.Win)
           {
               var units = this.HumanPlayer.GetUnitsByPosition(pos);
               var settlement = this.HumanPlayer.GetSettlementInfoByPosition(pos);
               if (units.Count() == 0
                   && settlement != null)
               {
                   this.HumanPlayer.OccupySettlement(settlement);
                   this.UnitMove(unit, pos);
               }
           }
            this.ShowRange(this.GetAttackRange(unit));
            this.LoadGameMap();
        }

        internal void UnitExploreMode(object unit)
        {
            this.FinitStateMachine.Input(unit as GameLibrary.Unit.UnitInfo,
                                         CommandType.UnitExplore);
            this.ShowRange(
                this.GetExploreRange(unit as GameLibrary.Unit.UnitInfo),
                CommandType.UnitExplore);
        }

        internal void UnitExplore(GameLibrary.Unit.UnitInfo unit, Position pos)
        {
            this.HumanPlayer.Explore(unit, pos);
            ShowRange(this.GetExploreRange(unit));
            this.LoadGameMap();
        }

        internal void UnitFortify(GameLibrary.Unit.UnitInfo unit)
        {
            this.HumanPlayer.FortifyUnit(unit);
            this.LoadGameMap();
        }

        internal void BuildSettlement(GameLibrary.Unit.UnitInfo unit)
        {
            if (unit.Current.AP >= 1)
            {
                this.HumanPlayer.BuildSettlement(unit, GameLibrary.Settlement.SettlementFlags.Village);
                this.LoadGameMap();
            }
            else
                System.Windows.MessageBox.Show("AP not enough.");
        }

        internal void DisbandUnit(GameLibrary.Unit.UnitInfo unit)
        {
            this.HumanPlayer.DisbandUnit(unit);
            this.LoadGameMap();
        }
        
        internal void UnitUpgradeMode(GameLibrary.Unit.UnitInfo unit)
        {
            FinitStateMachine.Input(info: unit,
                                    command: CommandType.UnitUpgrade);
            this.ShowTemplates();
        }
        internal void UnitUpgrad(GameLibrary.Unit.UnitInfo unit,
                             GameLibrary.Unit.Template template)
        {
            this.HumanPlayer.UpgradeUnit(unit, template);
            this.LoadGameMap();
        }
        #endregion

        #region settlement

        internal void Abandon(GameLibrary.Settlement.SettlementInfo settlement)
        {
            this.HumanPlayer.Abandon(settlement);
            this.LoadGameMap();
        }

        internal void BuildBuilding(GameLibrary.Settlement.SettlementInfo settlement, string type)
        {
            // do not refresh settlement info within one turn, leave for v0.4
            switch (type)
            {
                case "Road":
                    this.HumanPlayer.BuildBuildings(settlement, GameLibrary.Settlement.SettlementHelper.Road);
                    break;
                case "Factory":
                    this.HumanPlayer.BuildBuildings(settlement, GameLibrary.Settlement.SettlementHelper.Factory);
                    break;
                case "Plant":
                    this.HumanPlayer.BuildBuildings(settlement, GameLibrary.Settlement.SettlementHelper.Plant);
                    break;
                default :
                    throw new Exception("Other type should not exist.");
            }
            this.LoadGameMap();
        }

        internal void ChangeType(GameLibrary.Settlement.SettlementInfo settlement)
        {
            this.HumanPlayer.ChangeType(settlement);
            this.LoadGameMap();
        }
        
        internal void CityBuildNewUnitMode(GameLibrary.Settlement.SettlementInfo settlement)
        {
            FinitStateMachine.Input(command: CommandType.CityBuildUnit,
                                    info: settlement);
            this.ShowTemplates();
        }
        internal void CityBuildUnit(GameLibrary.Settlement.SettlementInfo settlement,
                                    GameLibrary.Unit.Template template)
        {
            this.HumanPlayer.BuildUnit(settlement, template);
            this.LoadGameMap();
        }

        internal void LinkToCityMode(object settlement)
        {
            FinitStateMachine.Input(settlement as GameLibrary.Settlement.SettlementInfo,
                                    CommandType.SettlementLink);
        }

        internal void LinkToCity(
            GameLibrary.Settlement.SettlementInfo settlementA,
            GameLibrary.Settlement.SettlementInfo settlementB)
        {
            this.HumanPlayer.LinkToCity(settlementA, settlementB);
            this.LoadGameMap();
        }

        #endregion

        #region template

        // create template
        internal void CreatTemplate()
        {
            UnitTemplateCreation view = this.GetTemplateCreationView();
            this.PlayerMap.ShowInfoInExpander(view);
        }

        private UnitTemplateCreation GetTemplateCreationView()
        {
            UnitTemplateCreation view = new UnitTemplateCreation();
            view.NewTemplate += this.NewTemplate;
            return view;
        }

        #endregion

        #region information

        internal void ShowPlayerInfo()
        {
            this.PlayerMap.ShowInfoInExpander(
                GetPlayerInfoView());
        }

        private PlayerInfo GetPlayerInfoView()
        {
            return new PlayerInfo(
                this.HumanPlayer.GetPlayerInfo());
        }

        internal void ShowUnits()
        {
            IEnumerable<GameLibrary.Unit.UnitInfo> units = this.HumanPlayer.GetAllUnitInfo();
            this.PlayerMap.ShowInfoInExpander(
                new ShowInfoList(
                    InformationProvider.GetUnitInformations(units)
                                )
                    );
        }

        internal void ShowSettlements()
        {
            IEnumerable<GameLibrary.Settlement.SettlementInfo> settlements =
                this.HumanPlayer.GetAllSettlementInfo();
            this.PlayerMap.ShowInfoInExpander(
                new ShowInfoList(
                    InformationProvider.GetSettlementInformations(settlements)
                                )
                    );
        }

        internal void ShowTemplates()
        {
            IEnumerable<GameLibrary.Unit.Template> templates = this.HumanPlayer.GetAllTemplates();
            ShowInfoList infoList = new ShowInfoList(templates);
            infoList.Select += this.SelectedItem;
            this.PlayerMap.ShowInfoInExpander(infoList);                
        }

        internal void ShowSettlementInfo(GameLibrary.Settlement.SettlementInfo settlement)
        {
            this.PlayerMap.ShowInfoInExpander(
                new SettlementInfo(
                    InformationProvider.GetSettlementInformation(settlement)
                    ));
        }

        internal void ShowUnitInfo(GameLibrary.Unit.UnitInfo unit)
        {
            this.PlayerMap.ShowInfoInExpander(
                new UnitInfo(
                    InformationProvider.GetUnitInformation(unit)
                    ));
        }

        internal void ShowRelationships()
        {
            this.PlayerMap.ShowInfoInExpander(
                new Relationships(
                InformationProvider.PrepareRelationshipInformation(
                    this.HumanPlayer.GetRelationships()
                                    )
                                )
                            );
        }

        internal void ShowTileInfo(GameLibrary.Tile.TileType tile)
        {
            this.PlayerMap.ShowInfoInExpander(
                new TileInfo(
                    InformationProvider.GetTileInformation(tile)
                    )
                    );
        }
     
        #endregion

        #region relationship
        internal void DeclearWar()
        {
            Views.DeclearWar dw = this.GetDeclearWarView();
            this.PlayerMap.ShowInfoInExpander(dw);
        }

        private DeclearWar GetDeclearWarView()
        {
            Views.DeclearWar dw = new DeclearWar();
            dw.DeclearWarHandler += this.DeclearWarOnPlayer;
            return dw;
        }

        #endregion

        #region events

        private void SelectedItem(object sender, SelectedItemEventArgs e)
        {
            GameLibrary.Unit.Template template = e.Item as GameLibrary.Unit.Template;
            FinitStateMachine.Input(template: template);
        }

        private void NewTemplate(object sender, NewTemplateEventArgs e)
        {
            GameLibrary.Unit.Template template = TemplateConveter.ConvertFromTemplateInfo(e.Template);
            this.HumanPlayer.AddTemplate(template);
            this.ClearPlayerMapExpander();
        }

        private void ClearPlayerMapExpander()
        {
            this.PlayerMap.ShowInfoInExpander(null);
        }

        private void MouseClickButton(object sender, MouseLeftButtonClickButtonEventArgs e)
        {
            Position pos = GetGameMapPositionFromUserInterfaceGridPosition(e.TileX, e.TileY);
            if (e.MapObjectX == 0 && e.MapObjectY == 0)
                FinitStateMachine.Input( info: GetSettlementInfoByPosition(pos));
            if (e.MapObjectX == 1 && e.MapObjectY == 1)
                FinitStateMachine.Input( position: pos);
        }

        private Position GetGameMapPositionFromUserInterfaceGridPosition(int x, int y)
        {
            return new Position(x, y) + this.CurrentUpperLeftCornerPosition;
        }

        private GameLibrary.Unit.UnitInfo GetUnitInfoByPosition(Position pos)
        {
            return this.HumanPlayer.GetUnitInfoByPosition(pos);
        }

        private GameLibrary.Settlement.SettlementInfo GetSettlementInfoByPosition(Position pos)
        {
           return this.HumanPlayer.GetSettlementInfoByPosition(pos + this.CurrentUpperLeftCornerPosition);
        }

        private void MouseClickGrid(object sender, MouseLeftButtonClickGridEventArgs e)
        {
            Position pos = GetGameMapPositionFromUserInterfaceGridPosition(e.X, e.Y);
            FinitStateMachine.Input(position: pos);
        }

        private void KeyPressed(object sender, KeyPressedEventArgs e)
        {
            this.PlayerMap.Move(e.Direction.X, e.Direction.Y);
        }

        private void DeclearWarOnPlayer(object sender, DeclearWarEventArgs e)
        {
            this.HumanPlayer.DeclearWar(e.PlayerID);
        }

        #endregion

        private Position GetPlayerMapPositionFromInputGridPosition(int x, int y)
        {
            Position pos = new Position(x, y) + this.CurrentUpperLeftCornerPosition;
            return pos;
        }

        private ObjectType GetObjectOnMapTypeByPosition(int x, int y)
        {
            Position pos = new Position(x, y);

            if (pos == Position.Zero)
                return ObjectType.Settlement;

            if (pos == Position.One)
                return ObjectType.Unit;

            throw new ArgumentException(string.Format("the position {0},{1} is not possible in this game", x, y));
        }

        #region get valid positions

        private IEnumerable<Position> GetAttackRange(GameLibrary.Unit.UnitInfo unit)
        {
            return this.RangeFinder.GetValidAttackPositions(
                                                unit,
                                                CurrentUpperLeftCornerPosition,
                                                GridHeight,
                                                GridWidth);
        }

        /// <summary>
        /// Return valid attack range not include unit location
        /// </summary>
        /// <param name="unit"></param>
        /// <returns></returns>
        internal IEnumerable<Position> GetValidUnitAttackRegionChangedToGameMapDimension(
            GameLibrary.Unit.UnitInfo unit)            
        {
            return from p in GetAttackRange(unit)
                   select p + unit.Location;
        }

        private int GridHeight
        {
            get
            {
                return this.PlayerMap.MapGrid.RowDefinitions.Count;
            }
        }

        private int GridWidth
        {
            get
            {
                return this.PlayerMap.MapGrid.ColumnDefinitions.Count;
            }
        }

        private IEnumerable<Position> GetMoveRange(GameLibrary.Unit.UnitInfo unit)
        {
            return this.RangeFinder.GetValidMovePosition(
                                                    unit,
                                                    CurrentUpperLeftCornerPosition,
                                                    GridHeight,
                                                    GridWidth);
        }

        /// <summary>
        /// Return valid move range nor include unit location
        /// </summary>
        /// <param name="unit"></param>
        /// <returns></returns>
        internal IEnumerable<Position> GetValidUnitMoveRegionChangedToGameMapDimension(GameLibrary.Unit.UnitInfo unit)
        {
            return from pos in GetMoveRange(unit)
                   select pos + unit.Location;
        }

        private IEnumerable<Position> GetExploreRange(GameLibrary.Unit.UnitInfo unit)
        {
            return this.RangeFinder.GetValidExplorePosition(
                                                   unit,
                                                   CurrentUpperLeftCornerPosition);
        }

        /// <summary>
        /// Return valid exploreable regions not include current unit region
        /// </summary>
        /// <param name="unit"></param>
        /// <returns></returns>
        internal IEnumerable<Position> GetValidUnitExploreRegionChangedToGameMapDimension(GameLibrary.Unit.UnitInfo unit)
        {
            return from pos in GetExploreRange(unit)
                   select pos + unit.Location;
        }

        #endregion

        #region about

        internal void ShowAbout()
        {
            this.PlayerMap.ShowInfoInExpander(
                new About());
        }

        #endregion
    }
}
